BemÀstra JavaScript Promise combinators (Promise.all, Promise.allSettled, Promise.race, Promise.any) för effektiv och robust asynkron programmering i globala applikationer.
JavaScript Promise Combinators: Avancerade asynkrona mönster för globala applikationer
Asynkron programmering Ă€r en hörnsten i modern JavaScript, sĂ€rskilt nĂ€r man bygger webbapplikationer som interagerar med API:er, databaser eller utför tidskrĂ€vande operationer. JavaScript Promises erbjuder en kraftfull abstraktion för att hantera asynkrona operationer, men för att bemĂ€stra dem krĂ€vs förstĂ„else för avancerade mönster. Denna artikel fördjupar sig i JavaScript Promise combinators â Promise.all, Promise.allSettled, Promise.race och Promise.any â och hur de kan anvĂ€ndas för att skapa effektiva och robusta asynkrona arbetsflöden, sĂ€rskilt i kontexten av globala applikationer med varierande nĂ€tverksförhĂ„llanden och datakĂ€llor.
FörstÄelse för Promises: En snabb sammanfattning
Innan vi dyker in i combinators, lÄt oss snabbt repetera Promises. Ett Promise representerar det slutgiltiga resultatet av en asynkron operation. Det kan befinna sig i ett av tre tillstÄnd:
- Pending: Det initiala tillstÄndet, varken uppfyllt eller avvisat.
- Fulfilled: Operationen slutfördes framgÄngsrikt, med ett resulterande vÀrde.
- Rejected: Operationen misslyckades, med en anledning (vanligtvis ett Error-objekt).
Promises erbjuder ett renare och mer hanterbart sÀtt att hantera asynkrona operationer jÀmfört med traditionella callbacks. De förbÀttrar kodens lÀsbarhet och förenklar felhantering. Avgörande Àr att de ocksÄ utgör grunden för de Promise combinators vi ska utforska.
Promise Combinators: Orkestrering av asynkrona operationer
Promise combinators Àr statiska metoder pÄ Promise-objektet som lÄter dig hantera och koordinera flera Promises. De tillhandahÄller kraftfulla verktyg för att bygga komplexa asynkrona arbetsflöden. LÄt oss granska var och en i detalj.
Promise.all(): Exekvera Promises parallellt och aggregera resultat
Promise.all() tar en itererbar samling (vanligtvis en array) av Promises som indata och returnerar ett enda Promise. Detta returnerade Promise uppfylls nÀr alla indata-Promises har uppfyllts. Om nÄgot av indata-Promises avvisas, avvisas det returnerade Promise omedelbart med anledningen frÄn det första avvisade Promise.
AnvÀndningsfall: NÀr du behöver hÀmta data frÄn flera API:er samtidigt och bearbeta de kombinerade resultaten Àr Promise.all() idealiskt. FörestÀll dig till exempel att du bygger en instrumentpanel som visar vÀderinformation frÄn olika stÀder runt om i vÀrlden. Varje stads data kan hÀmtas via ett separat API-anrop.
async function fetchWeatherData(city) {
try {
const response = await fetch(`https://api.example.com/weather?city=${city}`); // ErsÀtt med en riktig API-slutpunkt
if (!response.ok) {
throw new Error(`Kunde inte hÀmta vÀderdata för ${city}`);
}
return await response.json();
} catch (error) {
console.error(`Fel vid hÀmtning av vÀderdata för ${city}: ${error}`);
throw error; // Kasta om felet sÄ att det fÄngas av Promise.all
}
}
async function displayWeatherData() {
const cities = ['London', 'Tokyo', 'New York', 'Sydney'];
try {
const weatherDataPromises = cities.map(city => fetchWeatherData(city));
const weatherData = await Promise.all(weatherDataPromises);
weatherData.forEach((data, index) => {
console.log(`VĂ€der i ${cities[index]}:`, data);
// Uppdatera grÀnssnittet med vÀderdata
});
} catch (error) {
console.error('Kunde inte hÀmta vÀderdata för alla stÀder:', error);
// Visa ett felmeddelande för anvÀndaren
}
}
displayWeatherData();
Att tÀnka pÄ för globala applikationer:
- NÀtverkslatens: FörfrÄgningar till olika API:er pÄ olika geografiska platser kan uppleva varierande latens.
Promise.all()garanterar inte i vilken ordning Promises uppfylls, bara att de alla uppfylls (eller att ett avvisas) innan det kombinerade Promise avgörs. - API-hastighetsbegrÀnsning: Om du gör flera anrop till samma API eller flera API:er med delade hastighetsbegrÀnsningar kan du överskrida dessa grÀnser. Implementera strategier som att köa förfrÄgningar eller anvÀnda exponentiell backoff för att hantera hastighetsbegrÀnsningar pÄ ett smidigt sÀtt.
- Felhantering: Kom ihÄg att om nÄgot Promise avvisas, misslyckas hela
Promise.all()-operationen. Detta kanske inte Ă€r önskvĂ€rt om du vill visa partiell data Ă€ven om vissa förfrĂ„gningar misslyckas. ĂvervĂ€g att anvĂ€ndaPromise.allSettled()i sĂ„dana fall (förklaras nedan).
Promise.allSettled(): Hantera framgÄng och misslyckande individuellt
Promise.allSettled() liknar Promise.all(), men med en avgörande skillnad: den vÀntar pÄ att alla indata-Promises ska avgöras, oavsett om de uppfylls eller avvisas. Det returnerade Promise uppfylls alltid med en array av objekt, dÀr varje objekt beskriver resultatet av motsvarande indata-Promise. Varje objekt har en status-egenskap (antingen "fulfilled" eller "rejected") och en value- (om uppfyllt) eller reason- (om avvisat) egenskap.
AnvÀndningsfall: NÀr du behöver samla in resultat frÄn flera asynkrona operationer, och det Àr acceptabelt att vissa misslyckas utan att hela operationen misslyckas, Àr Promise.allSettled() det bÀttre valet. FörestÀll dig ett system som bearbetar betalningar via flera betalningsgateways. Du kanske vill försöka genomföra alla betalningar och registrera vilka som lyckades och vilka som misslyckades.
async function processPayment(paymentGateway, amount) {
try {
const response = await paymentGateway.process(amount); // ErsÀtt med en riktig betalningsgateway-integration
if (response.status === 'success') {
return { status: 'fulfilled', value: `Betalning bearbetad framgÄngsrikt via ${paymentGateway.name}` };
} else {
throw new Error(`Betalning misslyckades via ${paymentGateway.name}: ${response.message}`);
}
} catch (error) {
return { status: 'rejected', reason: `Betalning misslyckades via ${paymentGateway.name}: ${error.message}` };
}
}
async function processMultiplePayments(paymentGateways, amount) {
const paymentPromises = paymentGateways.map(gateway => processPayment(gateway, amount));
const results = await Promise.allSettled(paymentPromises);
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(result.value);
} else {
console.error(result.reason);
}
});
// Analysera resultaten för att avgöra övergripande framgÄng/misslyckande
const successfulPayments = results.filter(result => result.status === 'fulfilled').length;
const failedPayments = results.filter(result => result.status === 'rejected').length;
console.log(`Lyckade betalningar: ${successfulPayments}`);
console.log(`Misslyckade betalningar: ${failedPayments}`);
}
// Exempel pÄ betalningsgateways
const paymentGateways = [
{ name: 'PayPal', process: (amount) => Promise.resolve({ status: 'success', message: 'Betalning lyckad' }) },
{ name: 'Stripe', process: (amount) => Promise.reject({ status: 'error', message: 'OtillrÀckliga medel' }) },
{ name: 'Worldpay', process: (amount) => Promise.resolve({ status: 'success', message: 'Betalning lyckad' }) },
];
processMultiplePayments(paymentGateways, 100);
Att tÀnka pÄ för globala applikationer:
- Robusthet:
Promise.allSettled()förbÀttrar robustheten i dina applikationer genom att sÀkerstÀlla att alla asynkrona operationer försöks, Àven om vissa misslyckas. Detta Àr sÀrskilt viktigt i distribuerade system dÀr fel Àr vanliga. - Detaljerad rapportering: Resultat-arrayen ger detaljerad information om varje operations utfall, vilket gör att du kan logga fel, försöka misslyckade operationer pÄ nytt eller ge anvÀndarna specifik feedback.
- Partiell framgÄng: Du kan enkelt avgöra den totala framgÄngsgraden och vidta lÀmpliga ÄtgÀrder baserat pÄ antalet lyckade och misslyckade operationer. Du kan till exempel erbjuda alternativa betalningsmetoder om den primÀra gatewayen misslyckas.
Promise.race(): VĂ€lja det snabbaste resultatet
Promise.race() tar ocksÄ en itererbar samling av Promises som indata och returnerar ett enda Promise. Till skillnad frÄn Promise.all() och Promise.allSettled() avgörs dock Promise.race() sÄ snart nÄgot av indata-Promises avgörs (antingen uppfylls eller avvisas). Det returnerade Promise uppfylls eller avvisas med vÀrdet eller anledningen frÄn det första avgjorda Promise.
AnvÀndningsfall: NÀr du behöver vÀlja det snabbaste svaret frÄn flera kÀllor Àr Promise.race() ett bra val. FörestÀll dig att du frÄgar flera servrar efter samma data och anvÀnder det första svaret du fÄr. Detta kan förbÀttra prestanda och responsivitet, sÀrskilt i situationer dÀr vissa servrar kan vara tillfÀlligt otillgÀngliga eller lÄngsammare Àn andra.
async function fetchDataFromServer(serverURL) {
try {
const response = await fetch(serverURL, {signal: AbortSignal.timeout(5000)}); //LÀgg till en tidsgrÀns pÄ 5 sekunder
if (!response.ok) {
throw new Error(`Kunde inte hÀmta data frÄn ${serverURL}`);
}
return await response.json();
} catch (error) {
console.error(`Fel vid hÀmtning av data frÄn ${serverURL}: ${error}`);
throw error;
}
}
async function getFastestResponse() {
const serverURLs = [
'https://server1.example.com/data', // ErsÀtt med riktiga server-URL:er
'https://server2.example.com/data',
'https://server3.example.com/data',
];
try {
const dataPromises = serverURLs.map(serverURL => fetchDataFromServer(serverURL));
const fastestData = await Promise.race(dataPromises);
console.log('Snabbaste data mottagen:', fastestData);
// AnvÀnd den snabbaste datan
} catch (error) {
console.error('Kunde inte hÀmta data frÄn nÄgon server:', error);
// Hantera felet
}
}
getFastestResponse();
Att tÀnka pÄ för globala applikationer:
- TidsgrÀnser: Det Àr avgörande att implementera tidsgrÀnser nÀr du anvÀnder
Promise.race()för att förhindra att det returnerade Promise vĂ€ntar pĂ„ obestĂ€md tid om nĂ„gra av indata-Promises aldrig avgörs. Exemplet ovan anvĂ€nder `AbortSignal.timeout()` för att uppnĂ„ detta. - NĂ€tverksförhĂ„llanden: Den snabbaste servern kan variera beroende pĂ„ anvĂ€ndarens geografiska plats och nĂ€tverksförhĂ„llanden. ĂvervĂ€g att anvĂ€nda ett Content Delivery Network (CDN) för att distribuera ditt innehĂ„ll och förbĂ€ttra prestandan för anvĂ€ndare runt om i vĂ€rlden.
- Felhantering: Om det Promise som 'vinner' racet avvisas, avvisas hela Promise.race. Se till att varje Promise har lÀmplig felhantering för att förhindra ovÀntade avvisningar. Om det 'vinnande' Promise avvisas pÄ grund av en tidsgrÀns (som visas ovan), kommer de andra Promises att fortsÀtta exekveras i bakgrunden. Du kan behöva lÀgga till logik för att avbryta dessa andra Promises med `AbortController` om de inte lÀngre behövs.
Promise.any(): Acceptera den första uppfyllelsen
Promise.any() liknar Promise.race(), men med ett nÄgot annorlunda beteende. Den vÀntar pÄ att det första indata-Promise ska uppfyllas. Om alla indata-Promises avvisas, avvisas Promise.any() med ett AggregateError som innehÄller en array med anledningarna till avvisningarna.
AnvÀndningsfall: NÀr du behöver hÀmta data frÄn flera kÀllor och du bara bryr dig om det första framgÄngsrika resultatet Àr Promise.any() ett bra val. Detta Àr anvÀndbart nÀr du har redundanta datakÀllor ОлО alternativa API:er som ger samma information. Den prioriterar framgÄng över hastighet, eftersom den vÀntar pÄ den första uppfyllelsen, Àven om vissa Promises avvisas snabbt.
async function fetchDataFromSource(sourceURL) {
try {
const response = await fetch(sourceURL);
if (!response.ok) {
throw new Error(`Kunde inte hÀmta data frÄn ${sourceURL}`);
}
return await response.json();
} catch (error) {
console.error(`Fel vid hÀmtning av data frÄn ${sourceURL}: ${error}`);
throw error;
}
}
async function getFirstSuccessfulData() {
const dataSources = [
'https://source1.example.com/data', // ErsÀtt med riktiga datakÀllors URL:er
'https://source2.example.com/data',
'https://source3.example.com/data',
];
try {
const dataPromises = dataSources.map(sourceURL => fetchDataFromSource(sourceURL));
const data = await Promise.any(dataPromises);
console.log('Första framgÄngsrika data mottagen:', data);
// AnvÀnd den framgÄngsrika datan
} catch (error) {
if (error instanceof AggregateError) {
console.error('Kunde inte hÀmta data frÄn nÄgon kÀlla:', error.errors);
// Hantera felet
} else {
console.error('Ett ovÀntat fel intrÀffade:', error);
}
}
}
getFirstSuccessfulData();
Att tÀnka pÄ för globala applikationer:
- Redundans:
Promise.any()Àr sÀrskilt anvÀndbart nÀr man hanterar redundanta datakÀllor som ger liknande information. Om en kÀlla Àr otillgÀnglig eller lÄngsam kan du lita pÄ att de andra tillhandahÄller datan. - Felhantering: Se till att hantera det
AggregateErrorsom kastas nÀr alla indata-Promises avvisas. Detta fel innehÄller en array med de individuella anledningarna till avvisningarna, vilket gör att du kan felsöka och diagnostisera problemen. - Prioritering: Ordningen i vilken du tillhandahÄller Promises till
Promise.any()spelar roll. Placera de mest pÄlitliga eller snabbaste datakÀllorna först för att öka sannolikheten för ett framgÄngsrikt resultat.
Att vÀlja rÀtt combinator: En sammanfattning
HÀr Àr en snabb sammanfattning som hjÀlper dig att vÀlja lÀmplig Promise combinator för dina behov:
- Promise.all(): AnvÀnd nÀr du behöver att alla Promises uppfylls framgÄngsrikt, och du vill misslyckas omedelbart om nÄgot Promise avvisas.
- Promise.allSettled(): AnvÀnd nÀr du vill vÀnta pÄ att alla Promises ska avgöras, oavsett framgÄng eller misslyckande, och du behöver detaljerad information om varje utfall.
- Promise.race(): AnvÀnd nÀr du vill vÀlja det snabbaste resultatet frÄn flera Promises, och du bara bryr dig om det första som avgörs.
- Promise.any(): AnvÀnd nÀr du vill acceptera det första framgÄngsrika resultatet frÄn flera Promises, och det inte gör nÄgot om vissa Promises avvisas.
Avancerade mönster och bÀsta praxis
Utöver den grundlÀggande anvÀndningen av Promise combinators finns det flera avancerade mönster och bÀsta praxis att ha i Ätanke:
BegrÀnsa samtidighet
NÀr du hanterar ett stort antal Promises kan exekvering av dem alla parallellt överbelasta ditt system eller överskrida API-hastighetsbegrÀnsningar. Du kan begrÀnsa samtidigheten med hjÀlp av tekniker som:
- Chunking (Uppdelning i bitar): Dela upp Promises i mindre bitar och bearbeta varje bit sekventiellt.
- AnvÀnda en semafor: Implementera en semafor för att kontrollera antalet samtidiga operationer.
HÀr Àr ett exempel med chunking:
async function processInChunks(promises, chunkSize) {
const results = [];
for (let i = 0; i < promises.length; i += chunkSize) {
const chunk = promises.slice(i, i + chunkSize);
const chunkResults = await Promise.all(chunk);
results.push(...chunkResults);
}
return results;
}
// ExempelanvÀndning
const myPromises = [...Array(100)].map((_, i) => Promise.resolve(i)); //Skapa 100 promises
processInChunks(myPromises, 10) // Bearbeta 10 promises Ät gÄngen
.then(results => console.log('Alla promises har lösts:', results));
Hantera fel pÄ ett elegant sÀtt
Korrekt felhantering Ă€r avgörande nĂ€r man arbetar med Promises. AnvĂ€nd try...catch-block för att fĂ„nga fel som kan uppstĂ„ under asynkrona operationer. ĂvervĂ€g att anvĂ€nda bibliotek som p-retry eller retry för att automatiskt försöka misslyckade operationer pĂ„ nytt.
async function fetchDataWithRetry(url, retries = 3) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP-fel! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (retries > 0) {
console.log(`Försöker igen om 1 sekund... (Försök kvar: ${retries})`);
await new Promise(resolve => setTimeout(resolve, 1000)); // VĂ€nta 1 sekund
return fetchDataWithRetry(url, retries - 1);
} else {
console.error('Max antal försök nÄdda. Operationen misslyckades.');
throw error;
}
}
}
AnvÀnda Async/Await
async och await ger ett mer synkront liknande sÀtt att arbeta med Promises. De kan avsevÀrt förbÀttra kodens lÀsbarhet och underhÄllbarhet.
Kom ihÄg att anvÀnda try...catch-block runt await-uttryck för att hantera potentiella fel.
Avbrytande
I vissa scenarier kan du behöva avbryta vÀntande Promises, sÀrskilt nÀr du hanterar lÄngvariga operationer eller anvÀndarinitierade ÄtgÀrder. Du kan anvÀnda AbortController-API:et för att signalera att ett Promise ska avbrytas.
const controller = new AbortController();
const signal = controller.signal;
async function fetchDataWithCancellation(url) {
try {
const response = await fetch(url, { signal });
if (!response.ok) {
throw new Error(`HTTP-fel! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Fetch avbröts');
} else {
console.error('Fel vid hÀmtning av data:', error);
}
throw error;
}
}
fetchDataWithCancellation('https://api.example.com/data')
.then(data => console.log('Data mottagen:', data))
.catch(error => console.error('Fetch misslyckades:', error));
// Avbryt fetch-operationen efter 5 sekunder
setTimeout(() => {
controller.abort();
}, 5000);
Slutsats
JavaScript Promise combinators Àr kraftfulla verktyg för att bygga robusta och effektiva asynkrona applikationer. Genom att förstÄ nyanserna i Promise.all, Promise.allSettled, Promise.race och Promise.any kan du orkestrera komplexa asynkrona arbetsflöden, hantera fel pÄ ett elegant sÀtt och optimera prestanda. NÀr man utvecklar globala applikationer Àr det avgörande att ta hÀnsyn till nÀtverkslatens, API-hastighetsbegrÀnsningar och datakÀllors tillförlitlighet. Genom att tillÀmpa de mönster och bÀsta praxis som diskuteras i denna artikel kan du skapa JavaScript-applikationer som Àr bÄde prestandastarka och motstÄndskraftiga, och som levererar en överlÀgsen anvÀndarupplevelse till anvÀndare runt om i vÀrlden.